﻿// Copyright 2012 Stuart Turner
// Licensed under the Apache License, Version 2.0 [http://www.apache.org/licenses/LICENSE-2.0]

using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

using Complete.Data.Linq.Expressions;

namespace Complete.Data.Internal
{
	internal class QueryMaterializer : CompleteExpressionVisitor
	{
		private static MethodInfo _MaterializationFunction = typeof(QueryMaterializer).GetMethod("DoMaterialize", BindingFlags.Static | BindingFlags.NonPublic);
		private static List<T> DoMaterialize<T>(DbDataReader reader, CompleteContext context, Delegate del)
		{
			var func = (Func<DbDataReader, CompleteContext, T>)del;
			var l = new List<T>();
			while (reader.Read())
				l.Add(func(reader, context));
			return l;
		}

		private static MethodInfo _CacheFunction = typeof(CompleteContext).GetMethod("GetObject");
		private ParameterExpression _ContextParameter = Expression.Parameter(typeof(CompleteContext), "context");

		protected override Expression VisitColumn(ColumnExpression column)
		{
			return DataReaderFunctions.BuildDataReaderExpression(column.Ordinal, column.Type);
		}

		protected override Expression VisitMemberInit(MemberInitExpression init)
		{
			if (typeof(ITableWithKey).IsAssignableFrom(init.Type))
			{
				return
					Expression.Convert(
						Expression.Call(
							_ContextParameter,
							_CacheFunction,
							Expression.Convert(
								base.VisitMemberInit(init),
								typeof(ITableWithKey))),
						init.Type);
			}
			else
				return base.VisitMemberInit(init);
		}

		public static object Materialize(DbDataReader reader, LambdaExpression projection, CompleteContext context)
		{
			var qm = new QueryMaterializer();
			var mappingExpr =
				Expression.Lambda(
					qm.Visit(projection.Body),
					DataReaderFunctions.DATA_READER_PARAMETER,
					qm._ContextParameter);
			var mappingFunc = mappingExpr.Compile();

			var func = _MaterializationFunction.MakeGenericMethod(mappingExpr.ReturnType);

			return func.Invoke(null, new object[] { reader, context, mappingFunc });
		}
	}
}
